#include "stdafx.h"
#include "SpriteBatch.h"

#include "ErrorHandlers.h"
#include "Exception.h"
#include "Filesystem.h"
#include "Engine.h"

#define INDEX_BUFFER_NAME "SpriteBatchIB"
#define VERTEX_BUFFER_NAME "SpriteBatchVB"
#define DEFAULT_EFFECT_NAME "SpriteBatch"
#define DEFAULT_EFFECT_FILE  "SpriteBatch.fx"
#define TEXTURE_SEMANTIC "SPRITE_TEXTURE"

namespace Cvekas {

int SpriteBatch::vertex_buffer_name_counter = 0;

Sprite::Sprite()
{
}

Sprite::Sprite(const Sprite& s)
	:
source(s.source),
dest(s.dest),
color(s.color),
texture(s.texture),
layer(s.layer),
rotation(s.rotation)
{
}

bool Sprite::operator<(const Sprite& s)
{
	if(layer != s.layer)
		return layer < s.layer;
	
	if(texture.get() != s.texture.get())
		return texture.get() < s.texture.get();

	return false;
}

bool Sprite::operator==(const Sprite& s)
{
	if(layer != s.layer)
		return false;

	if(texture.get() != s.texture.get())
		return false;

	return true;
}

SpriteBatch::SpriteBatch(RendererPtr renderer, uint virtual_width, uint virtual_height, uint16 base_priority)
	:
renderer(renderer),
virtual_width(virtual_width),
virtual_height(virtual_height),
base_priority(base_priority),
sprite_count(0),
span_count(0),
presented(false)
{
	Engine e;

	EffectManagerPtr effect_manager = e.getVideoDevice()->getEffectManager();
	GpuBufferManagerPtr buffer_manager = e.getVideoDevice()->getGpuBufferManager();

	// Find or create default effect
	if(effect_manager->exists(DEFAULT_EFFECT_NAME))
		effect = effect_manager->get(DEFAULT_EFFECT_NAME);
	else
		effect = effect_manager->load(DEFAULT_EFFECT_NAME, Filesystem::redirect(DEFAULT_EFFECT_FILE));
	
	texture_param = effect->getParamBySemantic(TEXTURE_SEMANTIC);

	// Find or create index buffer
	if(buffer_manager->exists(INDEX_BUFFER_NAME))
		index_buffer = buffer_manager->getIndexBuffer(INDEX_BUFFER_NAME);
	else
	{
		index_buffer = buffer_manager->createIndexBuffer(INDEX_BUFFER_NAME, true, max_sprites * 6, false);
		build_ib();
	}

	// Create vertex buffer
	std::string vb_name = FORMAT(VERTEX_BUFFER_NAME "%d", % vertex_buffer_name_counter);
	vertex_buffer_name_counter++;

	vertex_buffer = buffer_manager->createVertexBuffer(vb_name, VertexPosUVColor::getFormat(), max_sprites * 4, true);

}

SpriteBatch::~SpriteBatch()
{
}
	
void SpriteBatch::present(uint technique)
{
	if(presented)
		throw EXCEPTION("Already presented this frame!");

	build_batches(technique);
	presented = true;
	renderer->prepare(*this);
}

void SpriteBatch::setEffect(EffectPtr effect)
{
	this->effect = effect;
	texture_param = effect->getParamBySemantic(TEXTURE_SEMANTIC);
}

void SpriteBatch::draw(Texture2DPtr texture, uint layer, const RectF &source, const RectF &dest, const Color &color, float rotation)
{
	if(sprite_count >= max_sprites - 1)
		throw EXCEPTION("Too many sprites!");

	RectF real_source;
	D3DSURFACE_DESC desc;
	
	if(!texture)
	{
		real_source = RectF(0.0f, 0.0f, 1.0f, 1.0f);
	}
	else
	{
		desc = texture->getTopLevelDescription();

		// Calculate real source
		real_source = source;
		real_source.x /= (float)desc.Width;
		real_source.y /= (float)desc.Height;

		if(FCMP(real_source.width, 0.0f) && FCMP(real_source.height, 0.0f))
		{
			real_source.width = 1.0f - real_source.x;
			real_source.height = 1.0f - real_source.y;
		}
		else
		{
			real_source.width /= (float)desc.Width;
			real_source.height /= (float)desc.Height;
		}
	}

	// Calculate real destination
	RectF real_dest = dest;
	real_dest.x = (real_dest.x / (float)virtual_width) * 2.0f - 1.0f;
	real_dest.y = (real_dest.y /(float)virtual_height) * -2.0f + 1.0f;

	// Offset by half pixel
	real_dest.x -= 1.0f / (float)virtual_width;
	real_dest.y += 1.0f / (float)virtual_height;

	if(FCMP(real_dest.width, 0.0f) && FCMP(real_dest.height, 0.0f) && texture)
	{
		real_dest.width = (real_source.width * (float)desc.Width) / (float)virtual_width;
		real_dest.height = -(real_source.height * (float)desc.Height) / (float)virtual_height;
	}
	else
	{
		real_dest.width /= (float)virtual_width;
		real_dest.height /= -(float)virtual_height;
	}
	real_dest.width *= 2.0f;
	real_dest.height *= 2.0f;

	Sprite new_sprite;
	new_sprite.source = real_source;
	new_sprite.dest = real_dest;
	new_sprite.color = color;
	new_sprite.texture = texture;
	new_sprite.layer = layer;
	new_sprite.rotation = rotation;

	sprites[sprite_count++] = new_sprite;
}

void SpriteBatch::draw(Texture2DPtr texture, uint layer, const RectF& source, const Vector2& location, const Color& color)
{
	RectF dest = RectF(location.x, location.y, 0.0f, 0.0f);
	draw(texture, layer, source, dest, color);
}

void SpriteBatch::draw(Texture2DPtr texture, uint layer, const Vector2& location, const Color& color)
{
	RectF dest = RectF(location.x, location.y, 0.0f, 0.0f);
	RectF source = RectF(0.0f, 0.0f, 0.0f, 0.0f);
	draw(texture, layer, source, dest, color);
}

void SpriteBatch::onPreRender(uint16 batch_id)
{
	if(!sprites[spans[static_cast<size_t>(batch_id)].first].texture)
		return;
	TexturePtr current_texture = boost::shared_polymorphic_cast<BaseTexture>(sprites[spans[static_cast<size_t>(batch_id)].first].texture);
	effect->setParamValue(texture_param, current_texture);
	effect->commitChanges();
}

uint SpriteBatch::getBatches(Cvekas::Batch **batches)
{
	if(!presented)
		return 0;

	*batches = this->batches.data();
	presented = false;
	
	sprite_count = 0;

	return span_count;
}

void SpriteBatch::build_ib()
{
	LOG("Filling SpriteBatch index buffer...");
	LockedBuffer locked_ib = index_buffer->lock(false);
	
	assert(locked_ib.size == max_sprites * 12);
	uint16* ib_data = reinterpret_cast<uint16*>(locked_ib.data);
	
	//uint16 v;
	for(uint i = 0, v = 0; i < max_sprites * 6; i += 6, v += 4)
	{
		ib_data[i] = v;
		ib_data[i+1] = v+1;
		ib_data[i+2] = v+2;
		ib_data[i+3] = v+2;
		ib_data[i+4] = v+1;
		ib_data[i+5] = v+3;
	}

	index_buffer->unlock();
}

void SpriteBatch::build_batches(uint technique)
{
	std::sort(sprites.begin(), sprites.begin() + sprite_count);

	// Build spans
	span_count = 0;
	uint i = 0;
	while(i < sprite_count)
	{
		uint span_length = 1;
		if(i < sprite_count-1)
		{
			while(sprites[i] == sprites[i + span_length])
			{
				span_length++;
				if(i + span_length >= sprite_count)
					break;
			}
		}

		SpriteSpan new_span;
		new_span.first = i;
		new_span.count = span_length;
		spans[span_count++] = new_span;
		i += span_length;
	}

	if(span_count >= max_spans)
		throw EXCEPTION("Span buffer overflow!");

	// Fill vertex buffer
	LockedBuffer locked_vb = vertex_buffer->lock(false);
	assert(locked_vb.size == max_sprites * 4 * VertexPosUVColor::getSize());
	VertexPosUVColor* vertices = reinterpret_cast<VertexPosUVColor*>(locked_vb.data);

	Vector2 fres = Vector2(float(virtual_width), float(virtual_height));
	for(uint i = 0; i < sprite_count; ++i)
	{
		Vector3 top_left = Vector3(sprites[i].dest.x, sprites[i].dest.y, 1.0f);
		Vector3 top_right = Vector3(sprites[i].dest.x + sprites[i].dest.width, sprites[i].dest.y, 1.0f);
		Vector3 bottom_left = Vector3(sprites[i].dest.x, sprites[i].dest.y + sprites[i].dest.height, 1.0f);
		Vector3 bottom_right = Vector3(sprites[i].dest.x + sprites[i].dest.width, sprites[i].dest.y + sprites[i].dest.height, 1.0f);

		// Rotate if neccessary
		if(sprites[i].rotation != 0.0f)
		{
			Vector2 tl(top_left.x * fres.x, top_left.y * fres.y); 
			Vector2 tr(top_right.x * fres.x, top_right.y * fres.y);
			Vector2 bl(bottom_left.x * fres.x, bottom_left.y * fres.y);
			Vector2 br(bottom_right.x * fres.x, bottom_right.y * fres.y);
			Vector2 c = (tl + br) / 2.0f;
			
			
			tl -= c; tr -= c; bl -= c; br -= c;
			tl.rotate(sprites[i].rotation);
			tr.rotate(sprites[i].rotation);
			bl.rotate(sprites[i].rotation);
			br.rotate(sprites[i].rotation);
			tl += c; tr += c; bl += c; br += c;

			top_left.x = tl.x / fres.x; top_left.y = tl.y / fres.y;
			top_right.x = tr.x / fres.x; top_right.y = tr.y / fres.y;
			bottom_left.x = bl.x / fres.x; bottom_left.y = bl.y / fres.y;
			bottom_right.x = br.x / fres.x; bottom_right.y = br.y / fres.y;
		}

		float left_u = sprites[i].source.x;
		float right_u = sprites[i].source.x + sprites[i].source.width;
		float top_v = sprites[i].source.y;
		float bottom_v = sprites[i].source.y + sprites[i].source.height;

		D3DCOLOR color = (D3DCOLOR)sprites[i].color;

		vertices[4*i] = VertexPosUVColor(top_left, left_u, top_v, color);
		vertices[4*i + 1] = VertexPosUVColor(top_right, right_u, top_v, color);
		vertices[4*i + 2] = VertexPosUVColor(bottom_left, left_u, bottom_v, color);
		vertices[4*i + 3] = VertexPosUVColor(bottom_right, right_u, bottom_v, color);
	}

	vertex_buffer->unlock();

	// Build batches
	for(uint i = 0; i < span_count; ++i)
	{
		Batch new_batch;
		
		assert(base_priority + sprites[spans[i].first].layer < MAX_UINT16);

		new_batch.priority = uint16(base_priority + sprites[spans[i].first].layer);
		new_batch.batch_id = static_cast<uint16>(i);
		new_batch.vb_id = vertex_buffer->getId();
		new_batch.ib_id = index_buffer->getId();
		new_batch.effect_id = effect->getId();
		new_batch.pass = 0;
		new_batch.technique = technique;
		new_batch.start_index = spans[i].first * 6; // 6 indexes for every sprite
		new_batch.tri_count = spans[i].count * 2; // 2 triangles for every sprite
		new_batch.owner = this;

		batches[i] = new_batch;
	}
}

} // namespace